using System;
using SharpMud.Server.Systems;
using System.ComponentModel;
using System.Collections.Generic;

namespace SharpMud.Server.App
{
	/// <summary>
	/// The heart of it all. Contains all the core functionality for a server application.
	/// This class is inherited by what we call a mudlib. *nod nod*
	/// </summary>
    [Browsable(true), Description("This is the MUD server application.")]
	public abstract class Application : Server.Systems.IServerSystemHost, IDisposable, IServiceProviderX
	{
        protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);

		#region Constructors
		public Application()
		{
			this._Status = AppStatus.UnInitialized;
            this._RegisteredServerSystems = new List<IServerSystem>();
            this.StatusChanged += new AppStatusChangeHandler(_this_StatusChanged);
		}

        void _this_StatusChanged(Application sender, AppStatusChangeEventArgs e)
        {
            log.InfoFormat("'{0}' -> '{1}'", e.OldStatus.ToString().ToUpper(), e.NewStatus.ToString().ToUpper());
        }
		#endregion

		#region Public Properties
		/// <summary>
		/// The security manager for the server application
		/// </summary>
		public Server.Systems.Security.SecurityManager			SecurityManager
		{
			get
			{
				return _SecurityManager;
			}
		}
		/// <summary>
		/// The status of the server application
		/// </summary>
        [Browsable(true), ReadOnly(true), Description("The execution status of the MUD server application.")]
		public AppStatus										Status
		{
			get
			{
				return this.__Status;
			}
		}
		
		/// <summary>
		/// This event is raised when the application's status changes
		/// </summary>
        [Browsable(false)]
		public event AppStatusChangeHandler StatusChanged
		{
			add
			{
				_StatusChanged += value;
			}
			remove
			{
				_StatusChanged -= value;
			}
		}
		#endregion
		#region Protected Properties
		/// <summary>
		/// The status of the application. This protected version of the property also
		/// allows change of the server's status. When the status is changed, the StatusChanged
		/// event is raised.
		/// </summary>
		protected AppStatus _Status
		{
			get
			{
				return this.__Status;
			}
			set
			{
				if(value!=this.__Status)
				{
					AppStatus o = this.__Status;
					this.__Status = value;
					if(this._StatusChanged!=null)
					{
						this._StatusChanged(this,new AppStatusChangeEventArgs(o,value));
					}
				}
			}
		}
		#endregion

		#region Private (and Internal) Variables (and a Property)
		private Server.Systems.Security.SecurityManager			_SecurityManager;
		private AppStatus										__Status;
		private bool _ShouldBeRunning;
		
		private System.Threading.Thread			_Thread;
		private System.TimeSpan					_DefaultPulseThreadDelay;

		internal System.Threading.Thread Thread
		{
			get
			{
				return this._Thread;
			}
		}
		#endregion
		#region Private Events
		private event AppStatusChangeHandler _StatusChanged;
		#endregion

		#region Security Manager Cross-Ties
		#region AppID
		/// <summary>
		/// This property retrieves the server app's GUID in a way that is globally
		/// accessible to trusted SharpMud.Server.App.Core classes
		/// </summary>
		internal System.Guid ___AppID
		{
			get
			{
				return AppID;
			}
		}
		/// <summary>
		/// This property retrieves the server app's secure key in a way that is globally
		/// accessible to trusted SharpMud.Server.App.Core classes
		/// </summary>
		internal string ___SecureAppKey
		{
			get
			{
				return SecureAppKey;
			}
		}
		/// <summary>
		/// This property, when overidden in a erived class, gets the GUID that
		/// uniquely identifies this SharpMud Server App instance
		/// </summary>
		protected abstract System.Guid	AppID {get;}
		/// <summary>
		/// This property, when overidden in a derived class, gets the secure key
		/// for the application. Typically something loaded from a secure configuration file.
		/// </summary>
		protected abstract string		SecureAppKey {get;}
		/// <summary>
		/// This is a helper method that derived classes may take advantage of to easily create
		/// a secure key to secure control of a SharpMud Server Application instance
		/// </summary>
		/// <returns></returns>
		protected string GenerateSecureAppKey()
		{
			return this.SecurityManager.GenerateSecureAppKey();
		}
		#endregion
		#endregion
		#region AppExecutionHandle Cross-Ties
		internal void ___Initialize()
		{
			lock(this)
			{
				this._Status = AppStatus.Initializing;
				
				this._SecurityManager = new SharpMud.Server.Systems.Security.SecurityManager();
				this.SecurityManager.Initialize(this);
                this.RegisterServerSystem(this._SecurityManager);
				this.SecurityManager.GoGlobal();

                log.Info(GetStartupDisplay());

                //Default pulse thread delay. After every pulse, sleep X amount.
                //Currently, set to 1/10th of a second.
                _DefaultPulseThreadDelay = TimeSpan.FromMilliseconds(100);
                log.InfoFormat("DefaultPulseThreadDelay = {0}ms", _DefaultPulseThreadDelay.TotalMilliseconds);
                log.InfoFormat("AppID                   = ", AppID.ToString());

				this.__Initialize();
				this._Status = AppStatus.Initialized;
			}
		}

		internal void ___Start()
		{
			if(this.Status==AppStatus.Paused)
				((IAppExecutionHandle)this).Resume();
			if(this.Status!=AppStatus.Initialized)
				return;

			_Thread = new System.Threading.Thread(new System.Threading.ThreadStart(this.PulseLoop));
			this._PreStart();
			this._Thread.Start();
		}

		internal void ___Pause()
		{
			if(this.Status!=AppStatus.Running)
				return;
			this._PrePause();
			this._Thread.Suspend();
			this._PostPause();
		}

		internal void ___Resume()
		{
			if(this.Status!=AppStatus.Paused)
				return;
			this._PreResume();
			this._Thread.Resume();
            this._Status = AppStatus.Running;
		}

		internal void ___Stop()
		{
			if(this.Status==AppStatus.Paused)
			{
                this.___Resume();
                this.___Stop();
				return;
			}
			if(this.Status!=AppStatus.Running)
				return;
			this._PreStop();
			this._Thread.Abort();
		}

		public void Dispose()
		{
            if (this._Status == AppStatus.Disposed)
                return;
            if (this._Status == AppStatus.Disposing)
                return;
            if (this._Status == AppStatus.UnInitialized)
            {
                this._Status = AppStatus.Disposing;
                this._Status = AppStatus.Disposed;
                return;
            }
            this._Status = AppStatus.Disposing;
            this.__Dispose();
            this._SecurityManager.RetractGlobal();
            this._Status = AppStatus.Disposed;
		}

		internal SharpMud.Server.App.AppStatus		___Status
		{
			get
			{
				return this._Status;
			}
		}

		internal event AppStatusChangeHandler ___StatusChanged
		{
			add
			{
				this._StatusChanged += value;
			}
			remove
			{
				this._StatusChanged -= value;
			}
		}
		#endregion

		#region App Execution Work
		#region Start
		/// <summary>
		/// This method should be called in Start(), before the thread is started.
		/// It insures that the server app is initialized and configured properly.
		/// </summary>
		private void _PreStart()
		{
			if(this.Status == AppStatus.Paused)
			{	throw new InvalidOperationException();	}

			_Status = AppStatus.Starting;
			//LogManager.Write("[SharpMud.Server.App.Core] STARTING...");
			this.__PreStart();            
			_ShouldBeRunning = true;
		}
		/// <summary>
		/// This method should be called within PulseLoop(), before the loop starts.
		/// </summary>
		private void _PostStart()
		{
			_Status = AppStatus.Running;
			//LogManager.Write("[SharpMud.Server.App.Core] RUNNING.");
			this.__PostStart();			
		}
		#endregion
		#region Stop
		/// <summary>
		/// This method should be called in Stop(), before the thread is stopped. It
		/// helps tell the server app that it should be stopping, and stops subsystems.
		/// </summary>
		private void _PreStop()
		{
			_Status = AppStatus.Stopping;
			//LogManager.Write("[SharpMud.Server.App.Core] STOPPING...");
			this.__PreStop();
			_ShouldBeRunning = false;
		}
		/// <summary>
		/// This method should be called in PulseLoop(), before the function exits. It
		/// makes sure configurations are saved, and the like.
		/// </summary>
		private void _PostStop()
		{
			SaveConfiguration();
			_Status = AppStatus.Stopped;
			//LogManager.Write("[SharpMud.Server.App.Core] STOPPED.");
			//LogManager.Write("");
			this.__PostStop();
			SaveConfiguration();
		}
		#endregion
		#region Resume
		/// <summary>
		/// This method should be called in Resume(), before the thread is resumed.
		/// Responsible for restarting subsystems.
		/// </summary>
		private void _PreResume()
		{
			this._Status = AppStatus.Resuming;
			//LogManager.Write("[SharpMud.Server.App.Core] RESUMING...");
			this.__PreResume();
		}
		#endregion
		#region Pause
		/// <summary>
		/// This method should be called in Pause(), before the thread is paused.
		/// It does stuff like pause? the subsystems
		/// </summary>
		private void _PrePause()
		{
			this._Status = AppStatus.Pausing;
			//LogManager.Write("[SharpMud.Server.App.Core] PAUSING...");
			this.__PrePause();
		}
		/// <summary>
		/// This method should be called in Pause(), after the thread is paused.
		/// Whatever may need to be done.
		/// </summary>
		private void _PostPause()
		{
			this._Status = AppStatus.Paused;
			//LogManager.Write("[SharpMud.Server.App.Core] PAUSED.");
			this.__PrePause();
		}
		#endregion
		#region PulseLoop
		/// <summary>
		/// This method is the workhorse function of the server. It is the stub
		/// for the central thread, and does all the looping, and all the thread
		/// delaying, which is essential for CPU effecient execution, as well as
		/// reliable timing of said execution.
		/// </summary>
		private void PulseLoop()
		{
			try
			{
				System.Threading.Thread.CurrentThread.IsBackground = false;
				this._PostStart();
				while(this._ShouldBeRunning)
				{
					this.__Pulse();
					System.Threading.Thread.Sleep(this.GetPulseThreadDelay());
				}
			}
			catch(System.Threading.ThreadAbortException e)
			{
				//This exception *can* be thrown when the exection thread is stopping,
				//so be prepared to catch it so that this function can finish its work
				//up as far as shutting down the server
			}
			finally
			{
				//No matter what, call this
				this._PostStop();
			}
		}
		#endregion
		#endregion
		#region MudLibRoot Tie-Ins (Virtuals)
		/// <summary>
		/// This method, when implemented by a derived class, performs globalized standardized
		/// mud tasks at a regularly occuring interval when the server is running.
		/// </summary>
		protected virtual void __Pulse()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to prepare the mud server to enter a state of running
		/// </summary>
		protected virtual void __PreStart()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that
		/// need to be performed as the server thread is starting
		/// </summary>
		protected virtual void __PostStart()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to perform before the server thread is stopped
		/// </summary>
		protected virtual void __PreStop()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to perform after the server thread is stopped, such as shutting
		/// down subsystems, and saving configuration
		/// </summary>
		protected virtual void __PostStop()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to perform before the server is paused.
		/// </summary>
		protected virtual void __PrePause()
		{
		}
		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to perform after the server is paused, such as pausing(?) subsystems
		/// </summary>
		protected virtual void __PostPause()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to perform before the thread is resumed, such as restarting
		/// subsystems
		/// </summary>
		protected virtual void __PreResume()
		{
		}

		/// <summary>
		/// This method, when implemented by a derived class, performs any tasks that are
		/// neccesary to initialize the mud server
		/// </summary>
		protected virtual void __Initialize()
		{
		}

        /// <summary>
        /// This method, when implemented by a derived class, performs any tasks that are
        /// neccesary to release resources used by the mud server
        /// </summary>
        protected virtual void __Dispose()
        {
        }

		/// <summary>
		/// This method, when overriden in a derived class, allows the server to dynamically
		/// specify how much the server execution thread should sleep after a pulse. When
		/// not overriden, uses a static value, configurable from Initialize()
		/// </summary>
		protected virtual System.TimeSpan GetPulseThreadDelay()
		{
			System.TimeSpan ts = TimeSpan.Zero;
			lock(this)
			{
				ts = _DefaultPulseThreadDelay;
			}
			return ts;
		}
		#endregion

		#region File System Tie-Ins
		/// <summary>
		/// This property gets/sets the root folder from which configuration files for 
		/// the server app and the mudlib may be retrieved.
		/// </summary>
		protected virtual string _RootConfigurationFolder
		{
			get
			{
				return Application.GetSubfolderIntelligently(RootDataFolder,"config","configuration");
			}
			set
			{
				throw new NotSupportedException("The RootConfigurationFolder cannot be changed when using the default implementation of that property.");
			}
		}
		/// <summary>
		/// This property gets/sets the root folder in which DLLs and EXEs for the server
		/// app and the mudlib reside
		/// </summary>
		protected virtual string _RootBinaryFolder
		{
			get
			{
				//If there is a "Bin" or a "Binaries" folder in the Root File Folder,
				//return the first one found as the RootBinFolder
				//Otherwise return RootFileFolder + ".\Bin"

				return Application.GetSubfolderIntelligently(RootFileFolder,"bin","binaries");
			}
			set
			{
				throw new NotSupportedException("The RootBinaryFolder cannot be changed when using the default implementation of that property.");
			}
		}
		/// <summary>
		/// This property gets/sets the root folder in which data for the MUD server is kept
		/// </summary>
		protected virtual string _RootDataFolder
		{
			get
			{
				return Application.GetSubfolderIntelligently(RootFileFolder,"data");
			}
			set
			{
				throw new NotSupportedException("The RootDataFolder cannot be changed when using the default implementation of that property.");
			}
		}
		/// <summary>
		/// This property gets/sets the root folder that SharpMud is residing in
		/// </summary>
		private string RootFileFolder
		{
			get
			{
				//If the current directory is "Bin" or "Binaries", then the Root File Folder
				//ist the next folder up, otherwise, use the current folder

				string CD = System.IO.Directory.GetCurrentDirectory();
				string CDn = (new System.IO.DirectoryInfo(CD)).Name;
				if(CDn.ToLower()=="bin" || CDn.ToLower()=="binaries")
				{
					return (new System.IO.DirectoryInfo(CD)).Parent.FullName;
				}
				else
				{
					return CD;
				}
			}
		}

		#endregion
		
		#region Configuration
		/// <summary>
		/// This method, when inherited in a derived class, will load the mud server's configuration.
		/// </summary>
		public virtual void LoadConfiguration()
		{
		}
		/// <summary>
		/// This method, when inherited in a derived class, will save the mud server's configuration.
		/// </summary>
		public virtual void SaveConfiguration()
		{
		}
		#endregion
		#region Misc
		/// <summary>
		/// Used to display system information as the system is booting up
		/// </summary>
		/// <returns></returns>
		protected virtual string GetStartupDisplay()
		{
			string s = "";
            string nl = System.Environment.NewLine;
            s = s + nl + nl;
			s = s + "[" + System.DateTime.Now.ToLongTimeString() + " " + System.DateTime.Now.ToLongDateString() + "]" + nl;
			s = s + System.Reflection.Assembly.GetCallingAssembly().GetName().Name + " Version " + System.Reflection.Assembly.GetCallingAssembly().GetName().Version.ToString() + nl;
			s = s + "Platform: " + System.Environment.OSVersion.Platform.ToString() + " " + System.Environment.OSVersion.Version.ToString();
			s = s + " [.NET CLR Version " + System.Environment.Version.ToString() + "]" + nl;
			s = s + System.Environment.UserName + "@" + System.Environment.UserDomainName + " on " + System.Environment.MachineName;
			if(System.Environment.UserInteractive)
			{
				s = s + " (Interactive Session)" + nl;
			}
			else
			{
                s = s + " (Non-Interactive Session)" + nl;
			}
			return s;
		}
		#endregion

		#region IServerSystemHost Members
		protected List<IServerSystem> _RegisteredServerSystems;

		protected void RegisterServerSystem(IServerSystem system)
		{
            lock (_RegisteredServerSystems)
            {
                if (this._RegisteredServerSystems.Contains(system) && system!=null)
                    return;
                this._RegisteredServerSystems.Add(system);
            }
		}

        [Browsable(true), DisplayName("Server Subsystems"), ReadOnly(true), Description("The MUD server application's subsystems.")]
        public IList<IServerSystem> ServerSystems
        {
            get
            {
                return ((IList<IServerSystem>)_RegisteredServerSystems);
            }
        }

        [Browsable(true), DisplayName("Root Data Folder"), ReadOnly(true), Description("The root folder for all data files for the MUD server.")]
		public string RootDataFolder
		{
			get
			{
				return this._RootDataFolder;
			}
		}

        [Browsable(true), DisplayName("Root Configuration Folder"), ReadOnly(true), Description("The folder where all configuration files for the MUD server are kept.")]
		public string RootConfigurationFolder
		{
			get
			{
				return this._RootConfigurationFolder;
			}
		}

        [Browsable(true), DisplayName("Root Binary Folder"), ReadOnly(true), Description("The folder where all binaries the MUD server needs are kept.")]
		public string RootBinaryFolder
		{
			get
			{
				return this._RootBinaryFolder;
			}
		}
		#endregion

		protected IAppExecutionHandle _GetAppExecHandle()
		{
			return new AppExecutionHandle(this);
		}

		#region Static Stuff
		
		/// <summary>
		/// Designed to find a compatibly (case insensitive) named subfolder with the given name
		/// within the given folder.
		/// </summary>
		/// <param name="parentPath">The parent folder to search in</param>
		/// <param name="subfolderName">The subfolder to search for</param>
		/// <returns>Returns the first compatible subfolder found. If none found, returns the suggested subfolder name.</returns>
		public static string GetSubfolderIntelligently(string parentPath, string subfolderName)
		{
			if(parentPath==null)
				throw new ArgumentNullException("parentPath");
			if(parentPath==String.Empty)
				throw new ArgumentNullException("parentPath","The parent path cannot be blank.");
			if(subfolderName==null)
				throw new ArgumentNullException("subfolderName");
			if(subfolderName==String.Empty)
				throw new ArgumentNullException("subfolderName","The subfolder name cannot be blank.");
			if(subfolderName.IndexOfAny(System.IO.Path.InvalidPathChars)>=0)
				throw new ArgumentException("The subfolder name must be a valid subfolder name.","subfolderName");

			foreach(string fn in System.IO.Directory.GetDirectories(parentPath))
			{
				if(fn.Trim().ToLower()==subfolderName.Trim().ToLower())
				{
					return System.IO.Path.Combine(parentPath,fn);
				}
			}
			
			return System.IO.Path.Combine(parentPath,subfolderName);
		}		
		/// <summary>
		/// Designed to find a compatibly (case insensitive) named subfile with the given name
		/// within the given folder.
		/// </summary>
		/// <param name="parentPath">The parent folder to search in</param>
		/// <param name="subfolderName">The subfile to search for</param>
		/// <returns>Returns the first compatible subfile found. If none found, returns the suggested subfile name.</returns>
		public static string GetSubfileIntelligently(string parentPath, string subfileName)
		{
			if(parentPath==null)
				throw new ArgumentNullException("parentPath");
			if(parentPath==String.Empty)
				throw new ArgumentNullException("parentPath","The parent path cannot be blank.");
			if(subfileName==null)
				throw new ArgumentNullException("subfileName");
			if(subfileName==String.Empty)
				throw new ArgumentNullException("subfileName","The subfile name cannot be blank.");
			if(subfileName.IndexOfAny(System.IO.Path.InvalidPathChars)>=0)
				throw new ArgumentException("The subfile name must be a valid file name.","subfileName");

			foreach(string fn in System.IO.Directory.GetFiles(parentPath))
			{
				if(fn.Trim().ToLower()==subfileName.Trim().ToLower())
				{
					return System.IO.Path.Combine(parentPath,fn);
				}
			}
			
			return System.IO.Path.Combine(parentPath,subfileName);
		}
		/// <summary>
		/// Designed to find a compatibly (case insensitive) named subfolder with the given name
		/// within the given folder. Allows for multiple possibilities for subfolder names.
		/// </summary>
		/// <param name="parentPath">The parent folder to search in</param>
		/// <param name="subfolderNames">The list of subfolders to search for</param>
		/// <returns>Returns the first compatible subfolder found. If none found, returns the first suggested subfolder name.</returns>
		public static string GetSubfolderIntelligently(string parentPath, params string[] subfolderNames)
		{
			if(parentPath==null)
				throw new ArgumentNullException("parentPath");
			if(parentPath==String.Empty)
				throw new ArgumentNullException("parentPath","The parent path must be an explicitly named directory.");
			if(subfolderNames==null)
				throw new ArgumentNullException("subfolderNames");
			if(subfolderNames.Length==0)
				throw new ArgumentNullException("subfolderNames","There must be at least 1 specified subfolder name.");
			if(subfolderNames.Length==1)
				return GetSubfolderIntelligently(parentPath,subfolderNames[0]);

			System.Collections.Specialized.StringCollection fldrs = new System.Collections.Specialized.StringCollection();
			foreach(string subfolderName in subfolderNames)
			{
				if(subfolderName==String.Empty)
					continue;
				fldrs.AddRange(System.IO.Directory.GetDirectories(parentPath,subfolderName.Trim().ToLower()));
			}
			System.Diagnostics.Debug.Assert(fldrs!=null);
			if(fldrs.Count==0)
				return System.IO.Path.Combine(parentPath,subfolderNames[0]);
			else
				return fldrs[0];
		}
		#endregion

        #region IServiceProviderX Members

        object IServiceProvider.GetService(Type serviceType)
        {
            lock (_RegisteredServerSystems)
            {
                foreach (IServerSystem o in _RegisteredServerSystems)
                {
                    if (serviceType.IsAssignableFrom(o.GetType()))
                        return o;
                }
            }
            return null;
        }

        T IServiceProviderX.GetService<T>()
        {
            if (typeof(T) == typeof(IServerSystem))
                return default(T);
            if (typeof(T) == typeof(IServerSystemHost))
                return default(T);

            lock (_RegisteredServerSystems)
            {
                foreach (IServerSystem o in _RegisteredServerSystems)
                {
                    if (typeof(T).IsAssignableFrom(o.GetType()))
                    {
                        return (T)o;
                    }
                }
            }
            return default(T);
        }

        #endregion
    }
}
